#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <time.h>
#include <sys/time.h>

// Valor máximo a ser gerado aleatoriamente
#define MAX 10000

typedef struct {
    int sockfd;
    int newsockfd;
    int port_number;
    int *vetor;
    int tamanho_vetor;
} thread_arg;

FILE *f;

// Variáveis para a contagem do tempo.
struct timeval t0,t1,t2,t3;
long tempo_total, tempo_ordenacao, tempo_envia, tempo_envia2, tempo_recebe;

/** void error(const char *msg)
* Função que apresenta o erro gerado no momento e sai do programa
*/
void error(const char *msg)
{
	perror(msg);
	exit(1);
}

/** void gerarAleatorio(int v[], int tam)
* Função que completa o vetor V[] com valores aleatórios, sendo tam, o tamanho do vetor
*/
void gerarAleatorio(int v[], int tam)
{
	int i;
	srand(time(NULL));

	for(i=0; i<tam; ++i)
		v[i]=(rand()%MAX)+1;
}

/** int comparar(const void *a, const void *b)
* Função que compara dois valores que serão ordenados pela função qsort()
* Retornando a diferença do valor ASCII dos valores de entrada
*/
int comparar(const void *a, const void *b)
{
	return (*(int*)a - *(int*)b);
}

/** void merge(int vetorSaida[], int vetorA[], int tamA, int vetorB[], int tamB)
 * Função que fará o merge ordenado dos dois vetores recebidos. 
 */
void merge(int vetorSaida[], int vetorA[], int tamA, int vetorB[], int tamB)
{
    int i = 0, j = 0, k;
    for (k = 0; k < tamA+tamB; k++) {
        if (i == tamA) {
           vetorSaida[k] = vetorB[j++];
           continue;
        }
        if (j == tamB) {
           vetorSaida[k] = vetorA[i++];
           continue;
        }
        if (vetorA[i] < vetorB[j])
            vetorSaida[k] = vetorA[i++];
        else
			vetorSaida[k] = vetorB[j++];
    }
}

/** int configuraCliente(char* hostName, struct hostent **server)
* Função responsável por abrir o socket de comunicação do cliente, utilizando TCP, pelo nome fornecido (hostName)
*/
int configuraCliente(char* hostName, struct hostent **server)
{
	int sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if (sockfd < 0)
		error("ERRO ao abrir socket");
	;
	if ((*server = gethostbyname(hostName)) == NULL)
		error("ERRO host não encontrado");
		
	return sockfd;
}

/** void abreConexaoCliente(struct sockaddr_in *serv_addr, struct hostent *server, int sockfd, int portaServidor)
* Função responsável por estabelecer uma conexão entre um cliente e um servidor, previamente configurados.
*/
void abreConexaoCliente(struct sockaddr_in *serv_addr, struct hostent *server, int sockfd, int portaServidor)
{
	bzero((char *) serv_addr, sizeof(*serv_addr));
	serv_addr->sin_family = AF_INET;
	bcopy((char *)server->h_addr, (char *)&(serv_addr->sin_addr.s_addr), server->h_length);
	serv_addr->sin_port = htons(portaServidor);
	if (connect(sockfd, (struct sockaddr *) serv_addr, sizeof(*serv_addr)) < 0)
		error("ERRO ao conectar");
}

/** int configuraServidor(int portaServidor)
* Função responsável por criar socket do servidor e linkar este com a porta da comunicação.
*/
int configuraServidor(int portaServidor)
{
	int sockfd;
	struct sockaddr_in serv_addr;
	
	if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
		error("ERROR opening socket");

	bzero((char *)&serv_addr, sizeof(serv_addr));

	serv_addr.sin_family = AF_INET;
	serv_addr.sin_addr.s_addr = INADDR_ANY;
	serv_addr.sin_port = htons(portaServidor);
	if (bind(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
		error("ERROR on binding");
	}

	listen(sockfd, 5);
	
	return sockfd;
}

/** void aceitarConexaoThr(thread_arg *thr_args, struct sockaddr_in *cli_addr)
* Atribui a uma thread a comunicação entre duas máquinas
*/
void aceitarConexaoThr(thread_arg *thr_args, struct sockaddr_in *cli_addr)
{
	thr_args->newsockfd = aceitarConexao(thr_args->sockfd, cli_addr);
}

/** int aceitarConexao(int sockfd, struct sockaddr_in *cli_addr)
* Função Accept com tratamento de erro. Função Accept aceita uma conexão solicitada pelo cliente
*/
int aceitarConexao(int sockfd, struct sockaddr_in *cli_addr)
{
	socklen_t sockaddr_len = sizeof(*cli_addr);
	int newsockfd = accept(sockfd, (struct sockaddr *)cli_addr, &sockaddr_len);
	if (newsockfd < 0)
		error("ERROR on accept");
		
	return newsockfd;
}

/** void envia(int sockfd, void *msg, int tam)
* Envia um conjunto de dados via o socket informado nos argumentos da função.
*/
void envia(int sockfd, void *msg, int tam)
{
	if (write(sockfd, msg, tam) < 0)
		 error("ERRO ao escrever no socket");
}

/** void recebe(int sockfd, void *buffer, int tam)
* Função responsável por receber um conjunto de dados atravez de um socket.
*/
void recebe(int sockfd, void *buffer, int tam)
{
	bzero(buffer, tam);
	if (recv(sockfd, buffer, tam, MSG_WAITALL) < 0)
		error("ERRO ao ler do socket");
}

/** char verificaACK(int sockfd)
* Função que recebe e verifica a mensagem de confirmação de recebimento de um certo conjunto de dados
*/
char verificaACK(int sockfd)
{
	char buffer[5];
	recebe(sockfd, buffer, 3);
	buffer[3] = '\0';
	if(strcmp(buffer, "ACK") == 0) {
		printf("Mensagem enviada.\n");
		return 1;
	}
	else {
		printf("ACK incorreto.\n");
		return 0;
	}
}

/** void enviaACK(int sockfd)
* Função que envia a mensagem de confirmação de recebimento de um conjunto de dados 
*/
void enviaACK(int sockfd)
{
	char ack_msg[5];
	strcpy(ack_msg, "ACK");
	envia(sockfd, ack_msg, strlen(ack_msg));
}

/** int recebeVetor(int sockfd, int **vetor, int *tamanho)
* Função que recebe um determinado conjunto de dados e os organiza em um vetor de acordo com o tamanho informado no argumento 
*/
void recebeSockVetor(int sockfd, int **vetor, int *tamanho)
{
	recebe(sockfd, tamanho, sizeof(int));
	printf("Tamanho vetor: %d\n", *tamanho);
	enviaACK(sockfd);
	
	*vetor = malloc(sizeof(int)*1000);
	recebe(sockfd, *vetor, sizeof(int) * (*tamanho));
	enviaACK(sockfd);
}

/** int recebeVetor(int sockfd, int **vetor, int *tamanho)
* Função que recebe um determinado conjunto de dados e os organiza em um vetor de acordo com o tamanho informado no argumento 
*/
int recebeVetor(int sockfd, int **vetor, int *tamanho)
{
	struct sockaddr_in cli_addr;
	
	int newsockfd = aceitarConexao(sockfd, &cli_addr);
	
	recebeSockVetor(newsockfd, vetor, tamanho);
	
	return newsockfd;
}

/** void *recebeVetorThr(void *args)
* Recebe o vetor, já organizado por threads. Um nó recebe o vetor de um nível inferior
*/
void *recebeVetorThr(void *args){
	thread_arg *thr_args = (thread_arg *) args;
	
	thr_args->newsockfd = recebeVetor(thr_args->sockfd, &thr_args->vetor, &thr_args->tamanho_vetor);
	
	pthread_exit(NULL);
}

/** void recebeVetorPorta(int porta, int **vetor, int *tamanho)
* Recebe o vetor de uma porta informada nos argumentos. Um nó recebe o vetor de um nível superior
*/
void recebeVetorPorta(int porta, int **vetor, int *tamanho)
{
	int sockfd = configuraServidor(porta);
	int newsockfd = recebeVetor(sockfd, vetor, tamanho);
	close(newsockfd);
	close(sockfd);
}

/** void enviaSockVetor(int sockfd,  int *vetor, int tam_vet)
* Envia vetor por um socket, com verificação de recebimento.
*/
void enviaSockVetor(int sockfd,  int *vetor, int tam_vet)
{
	// Envia o tamanho do vetor
	printf("Enviando tamanho do vetor... ");
	envia(sockfd, &tam_vet, sizeof(int));
	verificaACK(sockfd);

	// Envia o vetor para o servidor
	printf("Enviando vetor... ");
	envia(sockfd, vetor, sizeof(int)*tam_vet);
	verificaACK(sockfd);
}

/** void enviaVetor(char *destino, int porta,  int *vetor, int tam_vet)
* Envia vetor a qualquer momento. 
* Abre-se uma conexão e verifica-se o recebimento da mensagem 
*/
int enviaVetor(char *destino, int porta,  int *vetor, int tam_vet)
{
	int sockfd;
	struct hostent *servidor;
	struct sockaddr_in serv_addr;
	sockfd = configuraCliente(destino, &servidor);
	abreConexaoCliente(&serv_addr, servidor, sockfd, porta);
	enviaSockVetor(sockfd, vetor, tam_vet);
	return sockfd;
}

/** FILE* abreArquivo(char *arquivo)
* Função que abre um arquivo para escrita em diferentes nós com seus nomes diferentes
*/
FILE* abreArquivo(char *arquivo, char *modo)
{
	FILE *f;
	char nome[15];
	strcpy(nome, "log_");
	strcat(nome, arquivo);
	strcat(nome, ".txt");
	
	if ((f = fopen(nome, modo)) == NULL)
		error("Erro ao abrir arquivo");
	
	return f;
}

/** void gravaVetor(FILE *f, int *vetor, int tamanho)
* Função que grava um vetor em um arquivo, considerando o tamanho do arquivo expresso nos argumentos
*/
void gravaVetor(FILE *f, int *vetor, int tamanho)
{
	int i;
	for (i = 0; i < tamanho; i++)
		fprintf(f, "%d ", vetor[i]);
	fprintf(f, "\n\n");
}

/** void *abreHost(void *ssh_str)
* Função que executa o código nos diversos nós. Funcionamento semehante a um Script
*/
void *abreHost(void *ssh_str)
{
	printf("%s\n", (char*)ssh_str);
	system((char*)ssh_str);
	pthread_exit(NULL);
}

/** int main(int argc, char *argv[])
* Função Main 
*/
int main(int argc, char *argv[])
{
	int sockfd, newsockfd, porta;
	int portaServidor, totalHosts, proxHost, id, nivelHost, i, *vetor, *vetor_final, tamanho_vet = 125, tamanho_vet_final = 1000;
	char hosts[15][12], hosts_str[700], ssh_str[700];
	pthread_t thread1, thread2;
	thread_arg arg1, arg2;
	
	vetor = malloc(sizeof(int)*1000);
	vetor_final = malloc(sizeof(int)*1000);
	
	// Verifica o modo de funcionamento do Nó 
	char modo = atoi(argv[1]); // 0 = Normal; 1 = Reaproveitando hosts
	if (modo == 0)
		totalHosts = 15;
	else
		totalHosts = 8;
	
	id = atoi(argv[2]);
	porta = atoi(argv[3]);
	
	hosts_str[0] = '\0';
	for (i = 0; i < totalHosts; i++) {
		strcpy(hosts[i], argv[i+4]);
		strcat(hosts_str, " ");
		strcat(hosts_str, hosts[i]);
	}
	
	if (id >= 0) {
		f = abreArquivo(hosts[id], "w");
	}
	
	if (id == -1)
	{
		for (i = 0; i < totalHosts; i++) {
			sprintf(ssh_str, "ssh %s %s %d %d %d %s &", hosts[i], argv[0], modo, i, porta, hosts_str);
			system(ssh_str);
		}
		
		exit(0);
	}
	
	// Calcula o nível da árvore em que o nó se encontra
	if (modo == 0) {
		nivelHost = 0;
		while (id >= (1 << nivelHost)-1)
			nivelHost++;
		nivelHost--;
	}
	else
		nivelHost = 3;
	
	// Calcula o id do próximo host da árvora para enviar a mensagem
	if (id > 0) {
		proxHost = id % (1 << (nivelHost-1));
		if (modo == 0)
			proxHost += (1 << (nivelHost-1)) - 1;
	}
	else
		proxHost = 0;
		
	if (nivelHost < 0)
		error("Nivel incorreto");
		
	printf("id=%d; nivel=%d; proxHost=%s\n", id, nivelHost, hosts[proxHost]);
		
	if (nivelHost < 3) 
	{
		arg1.port_number = arg2.port_number = porta;
		arg1.sockfd = arg2.sockfd 	= configuraServidor(arg1.port_number);
		
		pthread_create(&thread1, NULL, recebeVetorThr, (void *)&arg1);
		pthread_create(&thread2, NULL, recebeVetorThr, (void *)&arg2);
		pthread_join(thread1, NULL);
		pthread_join(thread2, NULL);
		
		gettimeofday(&t0, 0);
		if (nivelHost == 2) {
			qsort(arg1.vetor, arg1.tamanho_vetor, sizeof(int), &comparar);
			qsort(arg2.vetor, arg2.tamanho_vetor, sizeof(int), &comparar);
		}
		
		merge(vetor, arg1.vetor, arg1.tamanho_vetor, arg2.vetor, arg2.tamanho_vetor);
		tamanho_vet = arg1.tamanho_vetor + arg2.tamanho_vetor;
		gettimeofday(&t1, 0);
    	tempo_ordenacao = t1.tv_usec - t0.tv_usec;
		
		fprintf(f, "Vetor combinado:\n");
		gravaVetor(f, vetor, tamanho_vet);
		fclose(f);
		f = NULL;
		
		if (nivelHost == 0) {
			tamanho_vet_final = tamanho_vet;
			for (i = 0; i < tamanho_vet_final; i++)
				vetor_final[i] = vetor[i];
		}
		else {
			// Envia para nivel superior
			gettimeofday(&t2, 0);
			sockfd = enviaVetor(hosts[proxHost], porta, vetor, tamanho_vet);
			gettimeofday(&t3, 0);
    		tempo_envia = t3.tv_usec - t2.tv_usec;
			
			// Recebe vetor pronto
			gettimeofday(&t2, 0);
			recebeSockVetor(sockfd, &vetor_final, &tamanho_vet_final);
			close(sockfd);
			gettimeofday(&t3, 0);
    		tempo_recebe = t3.tv_usec - t2.tv_usec;
			
			f = abreArquivo(hosts[id], "a");
			fprintf(f, "Vetor final:\n");
			gravaVetor(f, vetor_final, tamanho_vet_final);
			fclose(f);
			f = NULL;
		}
		
		// Envia para nivel inferior
		gettimeofday(&t2, 0);
		enviaSockVetor(arg1.newsockfd, vetor_final, tamanho_vet_final);
		enviaSockVetor(arg2.newsockfd, vetor_final, tamanho_vet_final);
		gettimeofday(&t3, 0);
    	tempo_envia2 = t3.tv_usec - t2.tv_usec;
    	f = abreArquivo(hosts[id], "a");
    	fprintf(f, "Tempo de ordenação: \t%ld us\n", tempo_ordenacao);
    	fprintf(f, "Tempo de envio superior: \t%ld us\nTempo de envio inferior: \t%ld us\n", tempo_envia, tempo_envia2);
    	fclose(f);
		f = NULL;
		
		close(arg1.newsockfd);
		close(arg2.newsockfd);
		close(arg1.sockfd);
		close(arg2.sockfd);
	}
	else
	{
		sleep(1);
		gettimeofday(&t0, 0);
		tamanho_vet = 125;
		gerarAleatorio(vetor, tamanho_vet);
		fprintf(f, "Vetor criado:\n");
		gravaVetor(f, vetor, tamanho_vet);
		fclose(f);
		f = NULL;

		gettimeofday(&t2, 0);
		sockfd = enviaVetor(hosts[proxHost], porta, vetor, 125);
		gettimeofday(&t3, 0);
    	tempo_envia = t3.tv_usec - t2.tv_usec;
		
		// Recebe vetor pronto
		gettimeofday(&t2, 0);
		recebeSockVetor(sockfd, &vetor_final, &tamanho_vet_final);
		gettimeofday(&t3, 0);
    	tempo_recebe = t3.tv_usec - t2.tv_usec;
		
		f = abreArquivo(hosts[id], "a");
		fprintf(f, "Vetor final:\n");
		gravaVetor(f, vetor_final, tamanho_vet_final);
		gettimeofday(&t1, 0);
    	tempo_total = t1.tv_usec - t0.tv_usec;
		fprintf(f, "Tempo de envio: \t%ld us\nTempo total de ordenação: \t%ld us\n", tempo_envia, tempo_total);
		fclose(f);
		f = NULL;

		close(sockfd);
	}
	
	if (f)
		fclose(f);
	return 0;
}